package com.snts.synchronization.protocol.adapter;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Date;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import nu.xom.Attribute;
import nu.xom.Element;

import com.snts.synchronization.core.Change;
import com.snts.synchronization.core.ChangeColumn;
import com.snts.synchronization.core.ErrorObject;
import com.snts.synchronization.core.ExternalTable;
import com.snts.synchronization.core.SyncPackage;
import com.snts.synchronization.core.SyncTable;
import com.snts.synchronization.core.Synchronization;
import com.snts.synchronization.core.TimeStampWithTimeZone;
import com.snts.synchronization.core.UUID;
import com.snts.synchronization.garbage.Utils;
import com.snts.synchronization.protocol.XMLConstants;

public abstract class SyncPackagePA<T extends SyncPackage> extends BaseXMLProtocolAdapter<T> implements XMLConstants {

	private static final Log log = LogFactory.getLog(SyncPackagePA.class);

	protected DefaultHandler getSyncPackageParser(SyncPackage syncPackage, Synchronization synchronization) {
		return new SyncPackageParser(syncPackage, synchronization);
	}

	protected void addItems(SyncPackage syncPackage, Element parent) {
		if(log.isDebugEnabled()){
			log.debug("addItems(" + syncPackage.getClass().getName() + ");");
			for(Change change : syncPackage.getChanges()){
				log.debug("change table=" + change.getTable().getName() + " row-id=" + change.getRowId().toString());
			}
		}
		for (Change change : syncPackage.getChanges()) {
			Element changeEl = new Element(Change.Type.INSERT == change.getType() ? INSERT
					: Change.Type.UPDATE == change.getType() ? UPDATE : "unknown");
			changeEl.addAttribute(new Attribute(SCHEMA, change.getTable().getExternalTable().getSchema()));
			changeEl.addAttribute(new Attribute(TABLE, change.getTable().getExternalTable().getName()));
			changeEl.addAttribute(new Attribute(ROW_ID, change.getRowId().toString()));
			for (ChangeColumn changeColumn : change.getColumns()) {
				Element columnEl = new Element(COLUMN);
				columnEl.addAttribute(new Attribute(NAME, changeColumn.getColumn().getExternalName()));
				if (changeColumn.getValue() instanceof Date) {
					columnEl.appendChild(new TimeStampWithTimeZone(new Date().getTime(), (short) 0).toString());
				} else {
					log.debug(changeColumn.getValue().getClass().getName());
					columnEl.appendChild("" + changeColumn.getValue());
				}
				changeEl.appendChild(columnEl);
			}
			if (change.getTime() != null) {
				appendTextElement(changeEl, TIME, change.getTime().toString());
			} else {
				log.warn("time is not specified for change #" + change.getId());
			}
			parent.appendChild(changeEl);
		}
	}

	private static class SyncPackageParser extends StringBufferDefaultHandler {

		private SyncPackage syncPackage;
		private Synchronization synchronization;
		private Change change;
		private ChangeColumn changeColumn;

		/**
		 * @param syncPackage
		 * @param synchronization
		 */
		public SyncPackageParser(SyncPackage syncPackage, Synchronization synchronization) {
			super();
			this.syncPackage = syncPackage;
			this.synchronization = synchronization;
		}

		@Override
		public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
			log.debug("startElement[tag=\"" + qName + "\"]");
			if (COLUMN.equals(qName)) {
				if (change != null) {
					if (attributes.getValue(NAME) != null) {
						changeColumn = change.addColumn(change.getTable().getColumnByExternalName(attributes.getValue(NAME)));
					} else {
						// Simply ignore this column and put log about error
						changeColumn = null;
						log.error("attribute name mustn't be null");
					}
				}
			} else if (UPDATE.equals(qName) || INSERT.equals(qName)) {

				String schema = attributes.getValue(SCHEMA);
				if(schema != null) schema = schema.trim();
				if(schema == null || "".equals(schema)){
					schema = "public";
				}

				SyncTable syncTable = synchronization.getTableByExternalName(new ExternalTable(schema, attributes
						.getValue(TABLE).trim()));
				String rowId = attributes.getValue(ROW_ID);
				if (syncTable != null && UUID.isValidUUID(rowId)) {
					change = new Change(UPDATE.equals(qName) ? Change.Type.UPDATE : Change.Type.INSERT);
					change.setTable(syncTable);
					change.setRowId(new UUID(rowId));
					syncPackage.addChange(change);
				} else {
					log.warn("update has incorrect table name set " + attributes.getValue(NAME));
				}
				
			} else {
				super.startElement(uri, localName, qName, attributes);
			}

		}

		@Override
		protected void doEndElement(String uri, String localName, String qName, String textValue) throws SAXException {
			if (COLUMN.equals(qName) && changeColumn != null) {
				changeColumn.setAsString(textValue.trim());
			}
		}

	}

}
